module mirgame;


// Allegro 5 library pragmas..
const lib_path = ".\\resources\\allegro_lib\\";
pragma(lib, lib_path ~ "dallegro5");
pragma(lib, lib_path ~ "allegro");
pragma(lib, lib_path ~ "allegro_primitives");
pragma(lib, lib_path ~ "allegro_image");
pragma(lib, lib_path ~ "allegro_font");
pragma(lib, lib_path ~ "allegro_ttf");
pragma(lib, lib_path ~ "allegro_color");
pragma(lib, lib_path ~ "allegro_audio");
pragma(lib, lib_path ~ "allegro_acodec");


// Phobos imports
import std.stdio, std.conv, std.datetime, std.string, std.format;

// Application imports
import mirdefine, mirshare, mirsound, mirevent;

import common.imagehandler, common.sprite, common.clientsocket, common.pen;

import scene.scene, scene.intro.intro, scene.login.login, scene.serverselect.selectserver, scene.game.gameplay;
import scene.game.actor;

import share.utility, share.container, share.types, share.edcode, share.protocol;

// Allegro 5 imports
import allegro5.allegro;
import allegro5.allegro_native_dialog;
import allegro5.allegro_primitives;
import allegro5.allegro_image;
import allegro5.allegro_font;
import allegro5.allegro_ttf;
import allegro5.allegro_color;
import allegro5.allegro_audio;
import allegro5.allegro_acodec;
import allegro5.timer;

// AGui imports
import AGui.Backends.Allegro5.Allegro5;
import AGui.Color, AGui.Font, AGui.Graphics, AGui.Image, AGui.Widget, AGui.GuiManager;


bool initializeAllegro()
{
    // Initialize Allegro
    if (!al_init())
        return false;
    if (!al_init_image_addon())
        return false;
    
    al_init_font_addon();
    
    if (!al_init_ttf_addon())
        return false;
    if (!al_init_primitives_addon())
        return false;
    if (!al_init_acodec_addon())
        return false;
    
    if (!al_install_keyboard())
        return false;
    if (!al_install_mouse())
        return false;
    if (!al_install_audio())
        return false;
    
    return true;
}

bool finalizeAllegro()
{
    // Finalize Allegro
    if (al_is_keyboard_installed())
        al_uninstall_keyboard();
    
    if (al_is_mouse_installed())
        al_uninstall_mouse();
    
    if (al_is_audio_installed())
        al_uninstall_audio();

    if (al_is_system_installed())
        al_uninstall_system();
    
    return true;
}


int main(string[] args)
{
    initializeAllegro();

    ALLEGRO_CONFIG *cfg = al_get_system_config();
    if (cfg !is null)
        al_set_config_value(cfg, "directsound", "buffer_size", "8192"); //1024 * 16
    
    /*ALLEGRO_CONFIG* cfg = al_load_config_file("test.ini");
    version(Tango)
    {
    char[] arr = fromStringz(al_get_config_value(cfg, "", "name"));
    Stdout.formatln("Entry is: {}", arr);
    }*/

    MySocket = new ClientSocket;

    SoundMan = new SoundManager;
    SoundMan.InitMirSound;  
    
    SceneMan = new SceneManager;
    EventMan = new EventManager;
    
    SpriteInfo = new Sprite;
    SpriteInfo.InitSpriteInfo();
    SpriteInfo.SetInfo();

    MyPen = new Pen;
    MyPen.loadFont("arial", 9, 0);

    // -------------------------------------------------------
    MyGame = new Game;
    MyGame.initialize();
    
    int ret_code;
    while (true)
    {
        ret_code = MyGame.run();
        if (ret_code == -1)
            break;
        /*dwTime = Clock.currSystemTick();
        dwDelay = dwTime - dwLastTime;
        dwLastTime = dwTime;
        
        if ( dwDelay.msecs() != 0)
        {
            ret_code = MGWindow.run(dwDelay);
            if (ret_code == -1)
                break;
        }*/
    }

    destroy(MySocket);

    destroy(SoundMan);
    
    destroy(SceneMan);
    destroy(EventMan);
    destroy(SpriteInfo);
    MyPen.unloadFont();
    destroy(MyPen);

    destroy(MyGame);
    
    finalizeAllegro();
    
    return 1;
}


public class Game
{
    private ALLEGRO_DISPLAY* display = null;
    private ALLEGRO_EVENT_QUEUE* event_queue = null;
    private ALLEGRO_TIMER* update_timer = null;
    private ALLEGRO_TIMER* sock_message_timer = null;
    
    private Allegro5Input inputHandler = null;
    private Allegro5Graphics graphicsHandler = null;
    
    private Font defaultFont = null;
    private bool fps_unlimited = false;
    

    public this()
    {      
    }
    
    public ~this()
    {
        cleanUpAgui();

        if (update_timer !is null)
            al_destroy_timer(update_timer);
        if (sock_message_timer !is null)
            al_destroy_timer(sock_message_timer);


        if (event_queue !is null)
            al_destroy_event_queue(event_queue);
        if (display !is null)
            al_destroy_display(display);
    }
    
    void initializeAgui()
    {
        //Set the image loader
        Image.setImageLoader(new Allegro5ImageLoader);
        
        //Set the font loader
        Font.setFontLoader(new Allegro5FontLoader);
        
        //Instance the input handler
        inputHandler = new Allegro5Input();
        
        //Instance the graphics handler
        graphicsHandler = new Allegro5Graphics();
        
        //Allegro does not automatically premultiply alpha so let Agui do it
        Color.setPremultiplyAlpha(true);
        
        //Instance the gui
        guiMan = new GuiManager();
        
        //Set the input handler
        guiMan.setInput(inputHandler);
        
        //Set the graphics handler
        guiMan.setGraphics(graphicsHandler);
        
        //defaultFont = Font.load("Fonts/DejaVuSans.ttf", 9);
        defaultFont = Font.load("Fonts/arial.ttf", 9);
        
        //Setting a global font is required and failure to do so will crash.
        Widget.setGlobalFont(defaultFont);
    }
    
    void cleanUpAgui()
    {
        guiMan.getTop().clear();
        
        delete guiMan;
        guiMan = null;
        delete inputHandler;
        delete graphicsHandler;
        inputHandler = null;
        graphicsHandler = null;
        
        delete defaultFont;
        defaultFont = null;
    }
    
    public void initialize()
    {
        // Enable anti aliasing for the primitives
        //al_set_new_display_option(ALLEGRO_DISPLAY_OPTIONS.ALLEGRO_SAMPLE_BUFFERS, 1, ALLEGRO_SUGGEST);
        //al_set_new_display_option(ALLEGRO_DISPLAY_OPTIONS.ALLEGRO_SAMPLES, 8, ALLEGRO_SUGGEST);

        al_set_new_display_flags(ALLEGRO_WINDOWED);

        al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP | ALLEGRO_NO_PRESERVE_TEXTURE);

        // init display..
        display = al_create_display(SCREENWIDTH, SCREENHEIGHT);
        if (display is null)
        {
            writefln("failed to create display!");
            return;
        }

        //al_toggle_display_flag(display, ALLEGRO_FULLSCREEN_WINDOW, false);
        al_set_window_title(display, "MIR2");
        //al_set_window_position(display, 50, 50);
        
        //int flags = al_get_new_bitmap_flags();
        //al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT.ALLEGRO_PIXEL_FORMAT_RGB_565); // TODO: 565
        
        update_timer = al_create_timer(1.0f / FRAME_RATE);
        if (update_timer is null)
        {
            writefln("failed to create update_timer!");
            return;
        }

        sock_message_timer = al_create_timer(1.0f / SOCKET_MESSAGE_RATE);
        if (sock_message_timer is null)
        {
            writefln("failed to create sock_message_timer!");
            return;
        }
        
        // init event queue..
        event_queue = al_create_event_queue();
        al_register_event_source(event_queue, al_get_display_event_source(display));
        al_register_event_source(event_queue, al_get_keyboard_event_source());
        al_register_event_source(event_queue, al_get_mouse_event_source());
        al_register_event_source(event_queue, al_get_timer_event_source(update_timer));
        al_register_event_source(event_queue, al_get_timer_event_source(sock_message_timer));

        initializeAgui();


        SelectServerScene = new GameSelectServerScene(this);
        LoginScene = new GameLoginScene(this);
        PlayScene = new GamePlayScene(this);
        
        SceneMan.AddScene(SelectServerScene);
        SceneMan.AddScene(LoginScene);
        SceneMan.AddScene(PlayScene);
        SceneMan.ChangeScene(SceneType.Login);

        al_start_timer(update_timer);
        al_start_timer(sock_message_timer);
    }

    public void update()
    {
        //int rval = std.random.uniform(10, 20);

        guiMan.logic();
        SceneMan.UpdateScene;
    }
    
    public void render()
    {
        SceneMan.CalculateFrameRate();

        // Render the scene.
        SceneMan.RunScene();

        // Render the GUI.
        guiMan.render();

        // Show game information.
        showStatus(10, 10);
        
        al_flip_display();
    }
    
    
    public int run()
    {
        int ret_code = 0;
        static bool need_redraw = false;
        
        ALLEGRO_EVENT event;
        al_wait_for_event(event_queue, &event); // NOTE: this will save a "lot" of cpu time, compared to al_get_next_event..
        //while (al_get_next_event(event_queue, &event))
        //{
            //Let Agui process the event
            inputHandler.processEvent(event);
            
            if (event.type == ALLEGRO_EVENT_TIMER)
            {
                if (event.timer.source is update_timer)
                {
                    update();
                    need_redraw = true;
                }

                if (event.timer.source is sock_message_timer)
                {
                    MySocket.poll();
                }
            }
            else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
                ret_code = -1;
            else
                ret_code = doEvent(event);
        //}

        // No need to check al_is_event_queue_empty - once we break out of
        // al_get_next_event we can be sure it is empty :)
        if (need_redraw || fps_unlimited /*&& al_is_event_queue_empty(event_queue)*/)
        {
            need_redraw = false;
            render();
        }
        
        return ret_code;
    }

    private int doEvent(in ref ALLEGRO_EVENT event)
    {
        static IntPoint pt;
        int ret_code = 0;
        
        switch (event.type)
        {
            case ALLEGRO_EVENT_KEY_CHAR:
            {
                switch (event.keyboard.keycode)
                {
                    case ALLEGRO_KEY_ESCAPE:
                    {
                        ret_code = -1;
                        break;
                    }
                        
                    case ALLEGRO_KEY_F1:
                    {
                        toggleFullScreen();
                        break;
                    }
                        
                    default: break;
                }
            }

            case ALLEGRO_EVENT_KEY_DOWN:
            {
                SceneMan.KeyDown(event.keyboard.keycode);
                break;
            }

            case ALLEGRO_EVENT_KEY_UP:
            {
                SceneMan.KeyUp(event.keyboard.keycode);
                break;
            }
                
            case ALLEGRO_EVENT_MOUSE_AXES:
            {
                pt = IntPoint(event.mouse.x, event.mouse.y);
                SceneMan.MouseMove(pt);
                break;
            }
                
            case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
            {
                pt = IntPoint(event.mouse.x, event.mouse.y);
                SceneMan.MouseDown(pt);
                break;
            }
                
            case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
            {
                pt = IntPoint(event.mouse.x, event.mouse.y);
                SceneMan.MouseUp(pt);
                break;
            }
                
            default: break;
        }
        
        return ret_code;
    }
    
    public void showStatus(float nStartX, float nStartY)
    {
        string buffer = format("FPS: %s", SceneMan.CurrentFPS);
        MyPen.drawText(nStartX, nStartY, al_map_rgba(250, 250, 250, 255), AlBlack, buffer);
    }

    public void toggleFullScreen()
    {
        int flags = al_get_display_flags(display);
        
        if (flags & ALLEGRO_FULLSCREEN_WINDOW)
        {
            //al_set_display_flag(display, ALLEGRO_WINDOWED, false);
            al_set_display_flag(display, ALLEGRO_FULLSCREEN_WINDOW, false);
        }
        else
        {
            al_set_display_flag(display, ALLEGRO_FULLSCREEN_WINDOW, true);
            //al_set_display_flag(display, ALLEGRO_WINDOWED, true);
        }
    }

}